package eu.catengine.common.util;

import java.util.ArrayList;

import eu.catengine.CatEngineCore;
import eu.catengine.common.iface.IDisposable;
import eu.catengine.common.listener.InterpolatorListener;
import eu.catengine.primitive.Color;
import eu.catengine.primitive.Point;
import eu.catengine.primitive.Vector;
import eu.catengine.renderable.shape.Shape;
import eu.catengine.scene.Scene;

/**
 * Klasa służaca do zmiany pewnych wartości obiektu w czasie nie używając przy
 * tym wątków.
 * 
 * @author mbienkowski
 */
public class Interpolator implements IDisposable
{
    public static enum InterpType
    {
	moveBy, moveTo, colorBy, colorTo, scaleTo, scaleToAtPoint, scaleToAtCenter, scaleBy, scaleByAtPoint, scaleByAtCenter, rotateBy, rotateByAtPoint, rotateByAtCenter
    };

    private Scene mScene;
    private long mElapsedTime;
    private ArrayList<InterpAction> mInterpActionList;
    private InterpolatorListener mInterpolatorListener;

    /**
     * Konstruktor.<br>
     * Każda scena posiada interpolator, który <b>jest tworzony
     * automatycznie</b>.<br>
     * Nie należy wywoływać metody ręcznie.
     */
    public Interpolator(Scene scene)
    {
	mInterpActionList = new ArrayList<InterpAction>();
	mScene = scene;
    }

    /**
     * Dodaje obiekt nasłuchujący do interpolatora.
     */
    public void setInterpolatorListener(InterpolatorListener listener)
    {
	mInterpolatorListener = listener;
    }

    /**
     * Aktualizacja elementów <b>wywoływana automatycznie</b> z pętli gównej w
     * scenie.<br>
     * <br>
     * Nie należy wywoływać metody ręcznie.
     */
    public void update()
    {
	mElapsedTime = mScene.getElapsedTime();

	for (int i = mInterpActionList.size() - 1; i >= 0; i--)
	{
	    mInterpActionList.get(i).update();
	}
    }

    /**
     * Zatrzymuje interpolację.
     * 
     * @param object
     *            <code>Shape</code> Obiekt dla którego ma zostać zatrzymana
     *            interpolacja.
     */
    public void stopInterpolation(Shape object)
    {
	InterpAction ia;

	for (int i = mInterpActionList.size() - 1; i >= 0; i--)
	{
	    ia = mInterpActionList.get(i);

	    if (ia.getObject() == object)
	    {
		mInterpActionList.remove(i);

		if (mInterpolatorListener != null)
		{
		    mInterpolatorListener.onInterpolationStopped(ia.getObject(), ia.getInterpType());
		}
	    }
	}
    }

    /**
     * Zatrzymuje interpolację.
     * 
     * @param object
     *            <code>Shape</code> Obiekt dla którego ma zostać zatrzymana
     *            interpolacja.
     * @param type
     *            <code>InterpType</code> Typ interpolacji, która ma być
     *            zatrzymana.
     */
    public void stopInterpolation(Shape object, InterpType type)
    {
	InterpAction ia;

	for (int i = mInterpActionList.size() - 1; i >= 0; i--)
	{
	    ia = mInterpActionList.get(i);

	    if (ia.getObject() == object && ia.getInterpType().equals(type))
	    {
		mInterpActionList.remove(i);

		if (mInterpolatorListener != null)
		{
		    mInterpolatorListener.onInterpolationStopped(ia.getObject(), ia.getInterpType());
		}
	    }
	}
    }

    /**
     * Zatrzymuje interpolację.
     * 
     * @param object
     *            <code>Shape</code> Obiekt dla którego ma zostać zatrzymana
     *            interpolacja.
     * @param types
     *            <code>InterpType[]</code> Typy interpolacji, które mają być
     *            zatrzymane.
     */
    public void stopInterpolation(Shape object, InterpType[] types)
    {
	InterpAction ia;

	for (int i = mInterpActionList.size() - 1; i >= 0; i--)
	{
	    ia = mInterpActionList.get(i);

	    if (ia.getObject() == object)
	    {
		for (int j = 0; j < types.length; j++)
		{
		    if (ia.getInterpType().equals(types[j]))
		    {
			mInterpActionList.remove(i);

			if (mInterpolatorListener != null)
			{
			    mInterpolatorListener.onInterpolationStopped(ia.getObject(), ia.getInterpType());
			}
		    }
		}
	    }
	}
    }

    /**
     * Zmienia pozycję obiektu o podany wektor w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param x
     *            <code>float</code> Składowa X wektora przesunięcia.
     * @param y
     *            <code>float</code> Składowa Y wektora przesunięcia.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void moveBy(Shape object, float x, float y, long time)
    {
	mInterpActionList.add(new InterpMove(object, x, y, time, true));
    }

    /**
     * Zmienia pozycję obiektu na podany punkt w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param x
     *            <code>float</code> Współrzędna X punktu docelonego.
     * @param y
     *            <code>float</code> Współrzędna Y punktu docelonego.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void moveTo(Shape object, float x, float y, long time)
    {
	mInterpActionList.add(new InterpMove(object, x, y, time, false));
    }

    /**
     * Zmienia wartość koloru o podane składowe koloru w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param color
     *            (Color) Zmienna, której poszczególne składowe posłużą do
     *            zmiany barwy.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void colorBy(Shape object, Color color, long time)
    {
	mInterpActionList.add(new InterpColor(object, color, time, true));
    }

    /**
     * Zmienia wartość koloru do podanego koloru w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param color
     *            (Color) Zmienna, której poszczególne składowe posłużą do
     *            zmiany barwy.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void colorTo(Shape object, Color color, long time)
    {
	mInterpActionList.add(new InterpColor(object, color, time, false));
    }

    /**
     * Skaluje obiekt o podaną skalę względem punktu zaczepienia w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param scale
     *            <code>float</code> Skala dla osi X i Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleBy(Shape object, float scale, long time)
    {
	mInterpActionList.add(new InterpScale(0, 0, object, scale, scale, time, InterpType.scaleBy));
    }

    /**
     * Skaluje obiekt o podaną skalę względem punktu zaczepienia w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param scaleX
     *            <code>float</code> Skala dla osi X.
     * @param scaleY
     *            <code>float</code> Skala dla osi Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleBy(Shape object, float scaleX, float scaleY, long time)
    {
	mInterpActionList.add(new InterpScale(0, 0, object, scaleX, scaleY, time, InterpType.scaleBy));
    }

    /**
     * Skaluje obiekt o podaną skalę względem zadanego punktu w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param point
     *            <code>Point</code> Punkt skalowania.
     * @param scale
     *            <code>float</code> Skala dla osi X i Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleByAtPoint(Shape object, Point point, float scale, long time)
    {
	mInterpActionList.add(new InterpScale(point.x, point.y, object, scale, scale, time, InterpType.scaleByAtPoint));
    }

    /**
     * Skaluje obiekt o podaną skalę względem zadanego punktu w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param x
     *            <code>float</code> Współrzędna X dla punktu skalowania.
     * @param y
     *            <code>float</code> Współrzędna Y dla punktu skalowania.
     * @param scale
     *            <code>float</code> Skala dla osi X i Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleByAtPoint(Shape object, float x, float y, float scale, long time)
    {
	mInterpActionList.add(new InterpScale(x, y, object, scale, scale, time, InterpType.scaleByAtPoint));
    }

    /**
     * Skaluje obiekt o podaną skalę względem zadanego punktu w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param point
     *            <code>Point</code> Punkt skalowania.
     * @param scaleX
     *            <code>float</code> Skala dla osi X.
     * @param scaleY
     *            <code>float</code> Skala dla osi Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleByAtPoint(Shape object, Point point, float scaleX, float scaleY, long time)
    {
	mInterpActionList.add(new InterpScale(point.x, point.y, object, scaleX, scaleY, time, InterpType.scaleByAtPoint));
    }

    /**
     * Skaluje obiekt o podaną skalę względem zadanego punktu w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param x
     *            <code>float</code> Współrzędna X dla punktu skalowania.
     * @param y
     *            <code>float</code> Współrzędna Y dla punktu skalowania.
     * @param scaleX
     *            <code>float</code> Skala dla osi X.
     * @param scaleY
     *            <code>float</code> Skala dla osi Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleByAtPoint(Shape object, float x, float y, float scaleX, float scaleY, long time)
    {
	mInterpActionList.add(new InterpScale(x, y, object, scaleX, scaleY, time, InterpType.scaleByAtPoint));
    }

    /**
     * Skaluje obiekt o podaną skalę względem punktu centralnego w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param scale
     *            <code>float</code> Skala dla osi X i Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleByAtCenter(Shape object, float scale, long time)
    {
	mInterpActionList.add(new InterpScale(0, 0, object, scale, scale, time, InterpType.scaleByAtCenter));
    }

    /**
     * Skaluje obiekt o podaną skalę względem punktu centralnego w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param scaleX
     *            <code>float</code> Skala dla osi X.
     * @param scaleY
     *            <code>float</code> Skala dla osi Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleByAtCenter(Shape object, float scaleX, float scaleY, long time)
    {
	mInterpActionList.add(new InterpScale(0, 0, object, scaleX, scaleY, time, InterpType.scaleByAtCenter));
    }

    /**
     * Skaluje obiekt do podanej skali względem punktu zaczepienia w czasie
     * time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param scale
     *            <code>float</code> Skala dla osi X i Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleTo(Shape object, float scale, long time)
    {
	mInterpActionList.add(new InterpScale(0, 0, object, scale, scale, time, InterpType.scaleTo));
    }

    /**
     * Skaluje obiekt do podanej skali względem punktu zaczepienia w czasie
     * time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param scaleX
     *            <code>float</code> Skala dla osi X.
     * @param scaleY
     *            <code>float</code> Skala dla osi Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleTo(Shape object, float scaleX, float scaleY, long time)
    {
	mInterpActionList.add(new InterpScale(0, 0, object, scaleX, scaleY, time, InterpType.scaleTo));
    }

    /**
     * Skaluje obiekt do podanej skali względem punktu w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param point
     *            <code>Point</code> Punkt skalowania.
     * @param scale
     *            <code>float</code> Skala dla osi X i Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleToAtPoint(Shape object, Point point, float scale, long time)
    {
	mInterpActionList.add(new InterpScale(point.x, point.y, object, scale, scale, time, InterpType.scaleToAtPoint));
    }

    /**
     * Skaluje obiekt do podanej skali względem punktu w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param x
     *            <code>float</code> Współrzędna X dla punktu skalowania.
     * @param y
     *            <code>float</code> Współrzędna Y dla punktu skalowania.
     * @param scale
     *            <code>float</code> Skala dla osi X i Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleToAtPoint(Shape object, float x, float y, float scale, long time)
    {
	mInterpActionList.add(new InterpScale(x, y, object, scale, scale, time, InterpType.scaleToAtPoint));
    }

    /**
     * Skaluje obiekt do podanej skali względem punktu w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param point
     *            <code>Point</code> Punkt skalowania.
     * @param scaleX
     *            <code>float</code> Skala dla osi X.
     * @param scaleY
     *            <code>float</code> Skala dla osi Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleToAtPoint(Shape object, Point point, float scaleX, float scaleY, long time)
    {
	mInterpActionList.add(new InterpScale(point.x, point.y, object, scaleX, scaleY, time, InterpType.scaleToAtPoint));
    }

    /**
     * Skaluje obiekt do podanej skali względem punktu w czasie time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param x
     *            <code>float</code> Współrzędna X dla punktu skalowania.
     * @param y
     *            <code>float</code> Współrzędna Y dla punktu skalowania.
     * @param scaleX
     *            <code>float</code> Skala dla osi X.
     * @param scaleY
     *            <code>float</code> Skala dla osi Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleToAtPoint(Shape object, float x, float y, float scaleX, float scaleY, long time)
    {
	mInterpActionList.add(new InterpScale(x, y, object, scaleX, scaleY, time, InterpType.scaleToAtPoint));
    }

    /**
     * Skaluje obiekt do podanej skali względem punktu centralnego w czasie
     * time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param scale
     *            <code>float</code> Skala dla osi X i Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleToAtCenter(Shape object, float scale, long time)
    {
	mInterpActionList.add(new InterpScale(0, 0, object, scale, scale, time, InterpType.scaleToAtCenter));
    }

    /**
     * Skaluje obiekt do podanej skali względem punktu centralnego w czasie
     * time.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param scaleX
     *            <code>float</code> Skala dla osi X.
     * @param scaleY
     *            <code>float</code> Skala dla osi Y.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void scaleToAtCenter(Shape object, float scaleX, float scaleY, long time)
    {
	mInterpActionList.add(new InterpScale(0, 0, object, scaleX, scaleY, time, InterpType.scaleToAtCenter));
    }

    /**
     * Obraca obiekt względem punktu zaczepienia o podany kąt w stopniach.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param angle
     *            <code>float</code> Kąt rotacji.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void rotateBy(Shape object, float angle, long time)
    {
	mInterpActionList.add(new InterpRotate(object, null, angle, time, InterpType.rotateBy));
    }

    /**
     * Obraca obiekt względem zadanego punktu o podany kąt w stopniach.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param rotatePoint
     *            <code>Point</code> Punkt względem, którego dokonujemy rotacji.
     * @param angle
     *            <code>float</code> Kąt rotacji.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void rotateByAtPoint(Shape object, Point rotatePoint, float angle, long time)
    {
	mInterpActionList.add(new InterpRotate(object, rotatePoint, angle, time, InterpType.rotateByAtPoint));
    }

    /**
     * Obraca obiekt względem punktu centralnego.
     * 
     * @param object
     *            <code>Shape</code> Obiekt, którego dotyczy zmiana.
     * @param angle
     *            <code>float</code> Kąt rotacji.
     * @param time
     *            <code>long</code> Czas podany w ms. Wartość musi być dodatnia.
     */
    public void rotateByAtCenter(Shape object, float angle, long time)
    {
	mInterpActionList.add(new InterpRotate(object, null, angle, time, InterpType.rotateByAtCenter));
    }

    abstract class InterpAction implements IDisposable
    {
	protected Shape mObject;
	protected InterpType mType;
	protected long mStartTime;
	protected int mActualFrame, mKeyFrames;

	public InterpAction(Shape object, long duration)
	{
	    if (duration == 0)
	    {
		duration = CatEngineCore.getFrameDuration();
	    }

	    mActualFrame = 0;
	    mObject = object;
	    mStartTime = mScene.getElapsedTime();
	    mKeyFrames = (int) Math.ceil(duration / CatEngineCore.getFrameDuration());
	}

	public Shape getObject()
	{
	    return mObject;
	}

	public InterpType getInterpType()
	{
	    return mType;
	}

	public void update()
	{
	    long time = mElapsedTime - mStartTime;

	    if (time > 0 && mActualFrame < mKeyFrames)
	    {
		mActualFrame++;
		updateForward();
	    }
	    else if (mActualFrame == mKeyFrames)
	    {
		mInterpActionList.remove(this);

		if (mInterpolatorListener != null)
		{
		    mInterpolatorListener.onInterpolationEnded(mObject, mType);
		}
	    }
	    else if (time < 0 && mActualFrame > 0)
	    {
		mActualFrame--;
		updateBackward();
	    }
	}

	@Override
	public void dispose()
	{
	    mObject = null;
	    mType = null;
	}

	abstract public void updateForward();

	abstract public void updateBackward();
    }

    class InterpMove extends InterpAction
    {
	Vector mDeltaVector;

	public InterpMove(Shape object, float x, float y, long duration, boolean moveBy)
	{
	    super(object, duration);
	    Point anchorPoint = object.getAnchorPoint();

	    if (moveBy)
	    {
		mType = InterpType.moveBy;
		mDeltaVector = new Vector(anchorPoint, new Point(anchorPoint.x + x, anchorPoint.y + y));
	    }
	    else
	    {
		mType = InterpType.moveTo;
		mDeltaVector = new Vector(anchorPoint, new Point(x, y));
	    }

	    mDeltaVector.divide((float) mKeyFrames);
	}

	public void updateForward()
	{
	    mObject.moveBy(mDeltaVector.x, mDeltaVector.y);
	}

	public void updateBackward()
	{
	    mObject.moveBy(-mDeltaVector.x, -mDeltaVector.y);
	}

	@Override
	public void dispose()
	{
	    mDeltaVector = null;
	    super.dispose();
	}
    }

    class InterpColor extends InterpAction
    {
	Color mDeltaColor;
	Color mColor;

	public InterpColor(Shape object, Color color, long duration, boolean colorBy)
	{
	    super(object, duration);
	    mColor = mObject.getColor();

	    if (colorBy)
	    {
		mType = InterpType.colorBy;
		mDeltaColor = new Color(color.red / mKeyFrames, color.green / mKeyFrames, color.blue / mKeyFrames, color.alpha / mKeyFrames);
	    }
	    else
	    {
		mType = InterpType.colorTo;
		mDeltaColor = new Color((color.red - mColor.red) / mKeyFrames, (color.green - mColor.green) / mKeyFrames, (color.blue - mColor.blue)
			/ mKeyFrames, (color.alpha - mColor.alpha) / mKeyFrames);
	    }
	}

	public void updateForward()
	{
	    mColor.red += mDeltaColor.red;
	    mColor.green += mDeltaColor.green;
	    mColor.blue += mDeltaColor.blue;
	    mColor.alpha += mDeltaColor.alpha;
	}

	public void updateBackward()
	{
	    mColor.red -= mDeltaColor.red;
	    mColor.green -= mDeltaColor.green;
	    mColor.blue -= mDeltaColor.blue;
	    mColor.alpha -= mDeltaColor.alpha;
	}

	@Override
	public void dispose()
	{
	    mDeltaColor = null;
	    mColor = null;
	    super.dispose();
	}
    }

    class InterpScale extends InterpAction
    {
	float mDeltaScaleX;
	float mDeltaScaleY;
	float mX, mY;

	public InterpScale(float x, float y, Shape object, float scaleX, float scaleY, long duration, InterpType type)
	{
	    super(object, duration);
	    mX = x;
	    mY = y;
	    mType = type;

	    if (mType == InterpType.scaleTo || mType == InterpType.scaleToAtPoint || mType == InterpType.scaleToAtCenter)
	    {
		mDeltaScaleX = (scaleX - object.getScaleX()) / mKeyFrames;
		mDeltaScaleY = (scaleY - object.getScaleY()) / mKeyFrames;
	    }
	    else
	    {
		mDeltaScaleX = scaleX / mKeyFrames;
		mDeltaScaleY = scaleY / mKeyFrames;
	    }
	}

	public void updateForward()
	{
	    float tmpX = mObject.getScaleX() + mDeltaScaleX;
	    float tmpY = mObject.getScaleY() + mDeltaScaleY;

	    if (mType == InterpType.scaleTo || mType == InterpType.scaleBy)
	    {
		mObject.scaleTo(tmpX, tmpY);
	    }
	    else if (mType == InterpType.scaleToAtPoint || mType == InterpType.scaleByAtPoint)
	    {
		mObject.scaleToAtPoint(mX, mY, tmpX, tmpY);
	    }
	    else if (mType == InterpType.scaleToAtCenter || mType == InterpType.scaleByAtCenter)
	    {
		mObject.scaleToAtCenter(tmpX, tmpY);
	    }
	}

	public void updateBackward()
	{
	    float tmpX = mObject.getScaleX() - mDeltaScaleX;
	    float tmpY = mObject.getScaleY() - mDeltaScaleY;

	    if (mType == InterpType.scaleTo || mType == InterpType.scaleBy)
	    {
		mObject.scaleTo(tmpX, tmpY);
	    }
	    else if (mType == InterpType.scaleToAtPoint || mType == InterpType.scaleByAtPoint)
	    {
		mObject.scaleToAtPoint(mX, mY, tmpX, tmpY);
	    }
	    else if (mType == InterpType.scaleToAtCenter || mType == InterpType.scaleByAtCenter)
	    {
		mObject.scaleToAtCenter(tmpX, tmpY);
	    }
	}

	@Override
	public void dispose()
	{
	    super.dispose();
	}
    }

    class InterpRotate extends InterpAction
    {
	Point mRotationPoint;
	float mDeltaAngle;

	public InterpRotate(Shape object, Point rotationPoint, float angle, long duration, InterpType type)
	{
	    super(object, duration);
	    mType = type;
	    mRotationPoint = rotationPoint;
	    mDeltaAngle = angle / mKeyFrames;
	}

	public void updateForward()
	{
	    if (mType == InterpType.rotateByAtPoint)
	    {
		mObject.rotateBy(mDeltaAngle);
	    }
	    else if (mType == InterpType.rotateByAtPoint)
	    {
		mObject.rotateByAtPoint(mRotationPoint, mDeltaAngle);
	    }
	    else if (mType == InterpType.rotateByAtCenter)
	    {
		mObject.rotateByAtPoint(mObject.getCenterPoint(), mDeltaAngle);
	    }
	}

	public void updateBackward()
	{
	    if (mType == InterpType.rotateByAtPoint)
	    {
		mObject.rotateBy(-mDeltaAngle);
	    }
	    else if (mType == InterpType.rotateByAtPoint)
	    {
		mObject.rotateByAtPoint(mRotationPoint, -mDeltaAngle);
	    }
	    else if (mType == InterpType.rotateByAtCenter)
	    {
		mObject.rotateByAtPoint(mObject.getCenterPoint(), -mDeltaAngle);
	    }
	}

	@Override
	public void dispose()
	{
	    mRotationPoint = null;
	    super.dispose();
	}
    }

    @Override
    public void dispose()
    {
	for (int i = 0; i < mInterpActionList.size(); i++)
	{
	    mInterpActionList.get(i).dispose();
	}

	mInterpActionList.clear();

	mScene = null;
	mInterpActionList = null;
	mInterpolatorListener = null;
    }
}